/** 
 * custom PixelStream 自定义像素流
 * ESSS v3.0.0 版本以后，返回信息中会附带 instanceId 属性，默认开启独立通信+官方像素流
 * 1. 已有连接server 后 new PixelStreaming(...server) , 创建官方像素流
 * 2. esmsg会用来新建一个 new WebSocket(esmsg) 简称 esMsgWsWrapper;
 * 3. 利用 esMsgWsWrapper来覆盖官方像素流服务中的消息发送this.pixelStream._webRtcController.sendMessageController.dataChannelSender.sendData
 * 4. viewer的鼠标事件来自于UE消息
 * 
 * {
    "server": "ws://localhost:9105",
    "instanceId": "c8806df8-7f39-4df2-bb35-b473c9063ec2",
    "esmsg": "ws://localhost:9107/esmsg",
    "status": "ok"
}
 */

import { createGuid, createNextAnimateFrameEvent, Event, react, createAnimateFrame, createInterval } from "xbsj-base";
import { ESUeViewer } from "../../index";
import { PSUEViewerWrapper } from "./PSUEViewerWrapper";
import { UeCloudViewerBase, UeEventsType, UeCallResultType, UeCallType, UeEventResultType, UeFuncResultWithIdType, UeFuncType, UeFuncWithIdType } from "../../uemsg";

export class CSTMPSViewer extends UeCloudViewerBase {
    private _esueViewerWrapper: PSUEViewerWrapper;
    get esueViewerWrapper() { return this._esueViewerWrapper; }

    private _ueFuncResultWithIdsEvent = this.dv(new Event<[ueFuncResultWithIds: UeFuncResultWithIdType[]]>());

    private _ueFuncWithIdAndResultCallbacks: [ueFuncWithId: UeFuncWithIdType, rresultCallbackAndUeFunc: { timeStamp: number, ueFunc: UeFuncType, resultCallback: (result: any) => void }][] = [];
    private _waitingUeFuncIdAndResultCallbacks: Map<string, { timeStamp: number, ueFunc: UeFuncType, resultCallback: (result: any) => void }> = new Map();

    private _waitingUeCalls: Set<UeCallType> = new Set();

    private _ueEvent = this.dv(new Event<[ueEvent: UeEventResultType]>());

    private _errorEvent = this.dv(new Event<[string]>());
    get errorEvent() { return this._errorEvent; }

    // private _closeEvent = this.dv(new Event<[string]>());
    // get closeEvent() { return this._closeEvent; }

    private _readyEvent = this.dv(new Event());
    get readyEvent() { return this._readyEvent; }

    private _esmsgwsopen = this.dv(react<boolean>(false));
    get esmsgwsopen() { return this._esmsgwsopen.value; }
    set esmsgwsopen(value: boolean) { this._esmsgwsopen.value = value; }
    get esmsgwsopenChanged() { return this._esmsgwsopen.changed; }

    private _videoinited = this.dv(react<boolean>(false));
    get videoinited() { return this._videoinited.value; }
    set videoinited(value: boolean) { this._videoinited.value = value; }
    get videoinitedChanged() { return this._videoinited.changed; }

    private _webRtcConnected = this.dv(react(false));
    get webRtcConnected() { return this._webRtcConnected.value; }
    set webRtcConnected(value: boolean) { this._webRtcConnected.value = value; }
    get webRtcConnectedChanged() { return this._webRtcConnected.changed; }

    resize(width: number, height: number) {
        this.esueViewerWrapper.resize(width, height);
    }

    constructor(container: HTMLDivElement, wsUri: string, esmsgWsUri: string | undefined, private _ueViewer: ESUeViewer) {
        super();
        console.log(`CSTMPSViewer init ! 以官方像素流 + 独立通信的模式启动!`);
        const ueViewer = this._ueViewer;
        this._esueViewerWrapper = this.dv(new PSUEViewerWrapper(container, wsUri, esmsgWsUri));

        const esueViewerWrapper = this._esueViewerWrapper;

        {
            const update = () => esueViewerWrapper.debug = ueViewer.debug ?? false;
            update();
            this.d(ueViewer.debugChanged.don(update));
        }

        let ready = false;

        {
            const init = () => {
                if (ready) return;
                const wsReady = esueViewerWrapper.webSocketReady;
                if (this.videoinited && this.webRtcConnected && wsReady) {
                    console.log("%c[EarthSDK: esmsgWebSocket && videoinited && webRtcConnected]", "background: #a6ec99; color: black");
                    const param = { eventType: 'init', params: {} };
                    esueViewerWrapper.emitUIInteractionForBigData(param);
                    this._readyEvent.emit();
                    ready = true;
                }
            }

            this.d(this.videoinitedChanged.don(() => { init() }));
            this.d(this.webRtcConnectedChanged.don(() => { init() }));
            this.d(this.esmsgwsopenChanged.don(() => { init() }));

        }

        this.d(esueViewerWrapper.event.don((eventName, eventDetailStr) => {
            if (eventName === 'error') {
                this._errorEvent.emit(eventDetailStr);
            } else if (eventName === 'ueevent') {
                const detailJson = JSON.parse(eventDetailStr);
                if (detailJson.t === 'cr') {
                    const ueCallResult = detailJson as UeCallResultType;
                    ueCallResult.frs && this._ueFuncResultWithIdsEvent.emit(ueCallResult.frs);
                    if (ueCallResult.ers) {
                        for (let er of ueCallResult.ers) {
                            this._ueEvent.emit(er);
                        }
                    }
                }

            } else if (eventName === 'webRtcConnected') {
                this.webRtcConnected = true;
            } else if (eventName === 'videoinited') {
                this.videoinited = true;
            } else if (eventName === 'esmsgwsopen') {
                this.esmsgwsopen = true;
            }
        }));

        {
            const animate = this.dv(createAnimateFrame());
            let lastWidth: number | undefined;
            let lastHeight: number | undefined;

            animate.start(() => {
                if (!(lastWidth !== container.offsetWidth || lastHeight !== container.offsetHeight)) {
                    return;
                }
                lastWidth = container.offsetWidth;
                lastHeight = container.offsetHeight;
                if (lastWidth === undefined || lastHeight === undefined || !Number.isFinite(lastWidth) || !Number.isFinite(lastHeight) || lastWidth < 0 || lastHeight < 0) {
                    console.warn(`lastWidth === undefined || lastHeight === undefined || !Number.isFinite(lastWidth) || !Number.isFinite(lastHeight) || lastWidth < 0 || lastHeight < 0 ${lastWidth} ${lastHeight}`);
                    return;
                }
                esueViewerWrapper.resize(lastWidth, lastHeight);
                console.log(`resize: ${lastWidth} ${lastHeight}`);
            });
        }

        this.d(this._ueFuncResultWithIdsEvent.don(ueFuncResultWithIds => {
            for (const ueFuncResultWithId of ueFuncResultWithIds) {
                const resultCallbackAndUeFunc = this._waitingUeFuncIdAndResultCallbacks.get(ueFuncResultWithId.fid);
                if (!resultCallbackAndUeFunc) {
                    continue;
                }
                this._waitingUeFuncIdAndResultCallbacks.delete(ueFuncResultWithId.fid);
                resultCallbackAndUeFunc.resultCallback(ueFuncResultWithId.r);
            }
        }));

        let ueCallLastNum = 0;
        const processUnitMessage = (fs: UeFuncWithIdType[]) => {
            const ueCall: UeCallType = {
                n: ueCallLastNum++,
                tt: Date.now(),
                t: 'c',
                fs,
            };

            this._waitingUeCalls.add(ueCall);
            if (ueViewer.debug) {
                console.group(
                    "%cxe2 => ue",
                    " background-color:green;color:#ffffff;font-weight:bold;padding:4px;border-radius:5px;"
                );
                console.log(
                    `%c${JSON.stringify(ueCall, undefined, '')}`,
                    " border:1px dashed green;border-radius:5px;padding:10px;line-height:25px;color:green;"
                );
                console.groupEnd();

                // console.log(`xe2期待的返回结果类似：\n${JSON.stringify({
                //     "t": "cr",
                //     "frs": ueCall.fs.map(e => ({
                //         fid: e.fid,
                //         r: {
                //             error: '',
                //         }
                //     })),
                //     "ers": [{
                //         et: 'click',
                //         eid: 'abcedf(当前事件的id)',
                //         objId: 'eeccsd(点击的对象ID)',
                //         cartesianIn3D: [100, 145, 324],
                //         screenPosition: [100, 200],
                //     }, {
                //         et: 'dbclick',
                //         eid: 'abcedf(当前事件的id)',
                //         objId: 'eeccsd(点击的对象ID)',
                //         cartesianIn3D: [100, 145, 324],
                //         screenPosition: [100, 200],
                //     }]
                // }, undefined, '    ')}
                // `)
                // console.log('');
            }
            // this._esueViewerWrapper.emitUIInteractionForBigData(ueCall);
            this._esueViewerWrapper.emitUIInteractionForBigData(ueCall, () => {
                this._waitingUeCalls.delete(ueCall);
            });
        }

        const processMessage = () => {
            if (!ready) return;
            if (this._ueFuncWithIdAndResultCallbacks.length === 0) return;

            const fs = this._ueFuncWithIdAndResultCallbacks.map((([e]) => e));

            for (const [ueFuncWithId, resultCallbackAndUeFunc] of this._ueFuncWithIdAndResultCallbacks) {
                this._waitingUeFuncIdAndResultCallbacks.set(ueFuncWithId.fid, resultCallbackAndUeFunc);
            }
            this._ueFuncWithIdAndResultCallbacks.length = 0;

            // 每十个消息一起发送。。
            // const fsl = fs.length;
            // for (let i = 0; i < fsl; i += 10) {
            //     const end = i + 10 < fsl ? i + 10 : fsl; 
            //     const fss = fs.slice(i, end);
            //     processUnitMessage(fss);
            // }

            {
                // 消息发送机制改进，每个消息块不超过30000字节！
                // 64501
                // const maxBytes = 30000;
                // @ts-ignore;
                const maxBytes = window.g_emitDescriptor_maxBytes;
                const fsWithSize = fs.map(e => ({ f: e, s: JSON.stringify(e).length }));

                let l = fsWithSize.length;
                let i = 0;
                let currentSize = 0;
                let currentFs: UeFuncWithIdType[] = [];
                do {
                    do {
                        const { f, s } = fsWithSize[i];
                        // if (s > maxBytes) {
                        //     console.error(`消息体过大，无法传送！s > maxBytes error!, 此处直接跳过！！`, f);
                        //     ++i;
                        // } else if (currentSize + s < maxBytes && currentFs.length < 20) {
                        //     currentFs.push(f);
                        //     ++i;
                        // } else {
                        //     break;
                        // }
                        // 消息体超大也要加
                        currentFs.push(f);
                        ++i;
                        if (currentSize + s > maxBytes || currentFs.length >= 20) {
                            break;
                        }
                    } while (i < l);

                    if (currentFs.length > 0) {
                        ueViewer.debug && console.log(`传送${currentFs.length}条消息...`)
                        // console.log(`传送${currentFs.length}条消息...`)
                        processUnitMessage([...currentFs]);
                        currentFs.length = 0;
                        currentSize = 0;
                    }
                } while (i < l);
            }
        };

        const interalProcessing = this.dv(createInterval());
        interalProcessing.start(processMessage, 50);

        this.d(() => console.log(`UeCloudViewer正在销毁...`));
    }

    callUeFunc<ResultType>(ueFunc: UeFuncType) {
        // if (this._ueViewer.debug ?? false) {
        //     console.log(`callUeFunc from UeCloudViewer: ${JSON.stringify(ueFunc)}`);
        // }
        return new Promise<ResultType>(resolve => {
            const fid = createGuid();
            const ueFuncWithId: UeFuncWithIdType = { fid, ...ueFunc };
            this._ueFuncWithIdAndResultCallbacks.push([ueFuncWithId, { timeStamp: Date.now(), ueFunc, resultCallback: resolve }]);
        });
    }

    disposableOnUeEvent<EventType extends keyof UeEventsType>(type: EventType, callback: (params: UeEventsType[EventType]) => void) {
        if (this._ueViewer.debug ?? false) {
            console.log(`disposableOnUeEvent from UeCloudViewer, type:${type}`);
        }

        const eid = createGuid();
        this.callUeFunc({
            f: 'addEventListener',
            p: {
                et: type,
                eid,
            }
        });

        const remove = () => {
            this.callUeFunc({
                f: 'removeEventListener',
                p: {
                    eid,
                }
            });
        };

        const d = this._ueEvent.don(ueEvent => {
            if (ueEvent.et === type && ueEvent.eid === eid) {
                callback(ueEvent as UeEventsType[EventType]);
            }
        });

        return () => {
            d();
            remove();
        };
    }
}
